#!/bin/sh -efu
### This file is covered by the GNU General Public License,
### which should be included with libshell as the file LICENSE.
### All copyright information are listed in the COPYING.

if [ -z "${__included_shell_ip_address-}" ]; then
__included_shell_ip_address=1

### Regexp for single byte
readonly regex_byte='([01]?[0-9][0-9]?|2[0-4][0-9]|25[0-5])'

### Regexp for 4-byte address
readonly regex_ipaddr="$regex_byte(\.$regex_byte){3}"

### Regexp for IPv4 address
###
### (http://en.wikipedia.org/wiki/IP_address)
###
### Some first-octet values have special meanings:
###
### * First octet 127 represents the local computer, regardless of what network
###   it is really in. This is useful when testing internal operations.
###
### * First octet 224 and above are reserved for special purposes such as
###   multicasting.
###
### Octets 0 and 255 are not acceptable values in some situations, but 0 can be used
### as the second and/or third octet (e.g. 10.2.0.100).
###
readonly __regex_fbyte='([1-9][0-9]?|1[0-9][0-9]|2[01][0-9]|22[0-3])'
readonly __regex_sbyte='([1]?[0-9][0-9]?|2[0-4][0-9]|25[0-4])'
readonly __regex_lbyte='([1]?[0-9][0-9]?|2[0-4][0-9]|25[0-4])'

readonly regex_ipv4="${__regex_fbyte}(\.${__regex_sbyte}){2}\.${__regex_lbyte}"

### Checks that given option value is a valid IPv4 address.
valid_ipv4()
{
	local ipaddr="$1"
	local i=0 byte

	byte="${ipaddr##*.}"
	ipaddr="${ipaddr%.$byte}"

	[ "$byte" -gt 0 ] && [ "$byte" -lt 255 ] 2>/dev/null ||
			return 1

	while [ $i -lt 3 ]; do
		byte="${ipaddr##*.}"

		[ "$byte" != "$ipaddr" ] ||
			break

		ipaddr="${ipaddr%.$byte}"

		[ "$byte" -ge 0 ] && [ "$byte" -lt 255 ] 2>/dev/null ||
			return 1

		i=$(($i+1))
	done

	[ $i -eq 2 ] &&
		[ "$byte" -ne 127 ] && [ "$byte" -gt 0 ] && [ "$byte" -lt 224 ] 2>/dev/null ||
		return 1
}

__ipv4_hex()
{
	[ -n "${1-}" ] ||
		return 2

	local IFS=.
	set -- $1

	local i=0
	for b; do
		[ "$b" -ge 0 ] && [ "$b" -le 255 ] 2>/dev/null ||
			return 2
		i=$(($i + 1))
	done

	[ "$i" -eq 4 ] ||
		return 2

	printf '0x'
	printf '%02x' "$@"
}

### Checks that IP address is in subnet
### Usage example:
### ipv4_ip_subnet 172.16.1.2 172.16.1.0/24; echo res=$?
### res=0
###
### ipv4_ip_subnet 172.16.3.2 172.16.1.0/24; echo res=$?
### res=1
ipv4_ip_subnet()
{
	local ip net prefix
	ip="${1-}"; shift
	net="${1-}"; shift
	prefix="${net##*/}"

	[ -n "$prefix" ] && [ "$prefix" -ge 0 ] 2>/dev/null ||
		return 2

	local hex_addr hex_net hex_mask p

	hex_addr="$(__ipv4_hex "$ip")" &&
	hex_net="$(__ipv4_hex "${net%%/*}")" ||
		return 2

	p=$((0xFFFFFFFF))
	hex_mask="$(($p - ($p >> $prefix)))"
	[ "$(($hex_net & $hex_mask))" -eq "$(($hex_addr & $hex_mask))" ] ||
		return 1
}

### Convert netmask to routing prefix.
### Usage example:
### ipv4_mask2prefix 255.255.0.0
### 16
###
### ipv4_prefix2mask 255.255.255.0
### 24
ipv4_mask2prefix()
{
	local hex_mask
	hex_mask="$(__ipv4_hex "${1-}")" ||
		return 2

	local p i=0 prefix=''

	p=$((~$hex_mask & 0xFFFFFFFF))

	while [ "$p" -ne 0 ]; do
		p=$(($p >> 1 & 0xFFFFFFFF))
		i=$(($i + 1))
	done
	prefix=$((32 - $i))

	[ "$prefix" -ge 0 ] && [ "$prefix" -le 32 ] ||
		return 1
	echo "$prefix"
}

### Convert routing prefix to netmask.
### Usage example:
### ipv4_prefix2mask 16
### 255.255.0.0
###
### ipv4_prefix2mask 24
### 255.255.255.0
ipv4_prefix2mask()
{
	local len
	len="${1-}"

	[ "$len" -ge 0 ] && [ "$len" -le 32 ] 2>/dev/null ||
		return 1

	local position=$((0xFFFFFFFF))
	local mask=$(($position - ($position >> $len)))

	printf '%s.%s.%s.%s\n' \
	    "$(($mask >> 24 & 0xFF))" \
	    "$(($mask >> 16 & 0xFF))" \
	    "$(($mask >> 8  & 0xFF))" \
	    "$(($mask       & 0xFF))"
}

fi #__included_shell_ip_address
